﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint.Administration.Health;
using SDS.SPHealthAnalyzerRules.Delegation.Helpers;

namespace SDS.SPHealthAnalyzerRules.Delegation.Rules
{
    /// <summary>
    /// Validates that the Claims to Windows Token Service (C2WTS) service account has the correct permissions on the SharePoint server.
    /// 
    /// Requirements:
    /// - Act as part of the operating system
    /// - Impersonate a client after authentication
    /// - Log on as a service
    /// </summary>
    /// <remarks>
    ///   Required by Excel Services <see cref="http://technet.microsoft.com/en-us/library/gg502605.aspx" />
    ///   Required by PerformancePoint <see cref="http://technet.microsoft.com/en-us/library/gg502603.aspx"/>
    ///   Required by Visio Services <see cref="http://technet.microsoft.com/en-us/library/gg502607.aspx" />
    ///   Required by SQL Reporting <see cref="http://msdn.microsoft.com/en-us/library/hh231678(v=sql.110).aspx" />
    /// </remarks>
    public class C2WTS_LocalSecurityPolicies : SPHealthAnalysisRule
    {
        private Guid CorrelationToken = Guid.NewGuid();
        List<string> Errors = new List<string>();
        List<string> Remedies = new List<string>();

        public override SPHealthCategory Category { get { return SPHealthCategory.Configuration; } }
        public override SPHealthCheckErrorLevel ErrorLevel { get { return SPHealthCheckErrorLevel.Error; } }

        public override SPHealthAnalysisRuleAutomaticExecutionParameters AutomaticExecutionParameters
        {
            get
            {
                return new SPHealthAnalysisRuleAutomaticExecutionParameters()
                {
                    Schedule = SPHealthCheckSchedule.Daily,
                    Scope = SPHealthCheckScope.All,
                    ServiceType = SPFarm.Local.Services.First(s => s.Name == "c2wts").GetType(),     // run on any server running C2WTS
                    RepairAutomatically = false
                };
            }
        }

        public override SPHealthCheckStatus Check()
        {
            var outval = SPHealthCheckStatus.Failed;

            LoggingService.Write(LoggingService.Category_C2WTS, TraceSeverity.Verbose, "Executing Health Check", CorrelationToken);

            Microsoft.SharePoint.SPSecurity.RunWithElevatedPrivileges(delegate { outval = doCheck(); });

            LoggingService.Write(
                LoggingService.Category_C2WTS,
                TraceSeverity.Verbose,
                String.Format("Health Check completed with outcome {0}", outval.ToString()),
                CorrelationToken);

            return outval;
        } // void Check()

        public override string Summary { get { return "DELEGATION: Claims to Windows Token Service (C2WTS) service account does not have permission"; } }
        public override string Explanation
        {
            get
            {
                return String.Format(
                    @"Claims to Windows Token Service is required for delegation by:" +
                    @"{0}Excel Services" +
                    @"{0}- http://technet.microsoft.com/en-us/library/gg502605.aspx" +
                    @"{0}Visio Services" +
                    @"{0}- http://technet.microsoft.com/en-us/library/gg502607.aspx" +
                    @"{0}PerformancePoint Services" +
                    @"{0}- http://technet.microsoft.com/en-us/library/gg502603.aspx" +
                    @"{0}SQL Server 2012 Reporting Services, Integrated Mode" +
                    @"{0}- http://msdn.microsoft.com/en-us/library/hh231678(v=sql.110).aspx" +
                    @"{0}" +
                    @"{0}The C2WTS service account must have the following permissions:" +
                    @"{0}- Act as part of the operating system" +
                    @"{0}- Impersonate a client after authentication" +
                    @"{0}- Log on as a service" +
                    @"{0}" +
                    @"{0}The following errors were found:" +
                    @"{0}{1}",

                    Environment.NewLine,
                    String.Join(Environment.NewLine, Errors.ToArray())
                    );
            }
        }
        public override string Remedy
        {
            get { 
                return String.Format(
                        @"The following corrective actions are required:" +
                        @"{0}{1}",

                        Environment.NewLine,
                        String.Join(Environment.NewLine, Remedies.ToArray())
                        );
            }
        }


        private SPHealthCheckStatus doCheck()
        {
            var outval = SPHealthCheckStatus.Failed;

            try
            {

                string SvcRunsAs = Helpers.Win32API.SvcConfigMgr.GetServiceAccount_W2CTS();

                // local system has SE_TCP_NAME and SE_IMPERSONATE_NAME, and SE_SERVICE_LOGON_NAME is redundant
                // ref: http://msdn.microsoft.com/en-us/library/ms684190(v=vs.85).aspx
                if (SvcRunsAs.ToLower() == "LocalSystem".ToLower())
                    return SPHealthCheckStatus.Passed;

                if (!Helpers.Win32API.Lsa.UserHasRights(SvcRunsAs, Helpers.Win32API.Lsa.Authorizations.SE_TCB_NAME))
                {
                    Errors.Add(String.Format("C2WTS service account [{0}] does not have permission to [{1}]", SvcRunsAs, "Act as part of the operating system"));
                    Remedies.Add(String.Format("Add [{0}] to the [{1}] security policy", SvcRunsAs, "Act as part of the operating system"));
                }

                if (!Helpers.Win32API.Lsa.UserHasRights(SvcRunsAs, Helpers.Win32API.Lsa.Authorizations.SE_IMPERSONATE_NAME))
                {
                    Errors.Add(String.Format("C2WTS service account [{0}] does not have permission to [{1}]", SvcRunsAs, "Impersonate a client after authentication"));
                    Remedies.Add(String.Format("Add [{0}] to the [{1}] security policy", SvcRunsAs, "Impersonate a client after authentication"));
                }

                if (!Helpers.Win32API.Lsa.UserHasRights(SvcRunsAs, Helpers.Win32API.Lsa.Authorizations.SE_SERVICE_LOGON_NAME))
                {
                    Errors.Add(String.Format("C2WTS service account [{0}] does not have permission to [{1}]", SvcRunsAs, "Log on as a service"));
                    Remedies.Add(String.Format("Add [{0}] to the [{1}] security policy", SvcRunsAs, "Log on as a service"));
                }

                if (Helpers.Win32API.Lsa.UserHasRights(SvcRunsAs, Helpers.Win32API.Lsa.Authorizations.SE_DENY_SERVICE_LOGON_NAME))
                {
                    Errors.Add(String.Format("C2WTS service account [{0}] has been DENIED permission to [{1}]", SvcRunsAs, "Log on as a service"));
                    Remedies.Add(String.Format("Remove [{0}] from the [DENY {1}] security policy", SvcRunsAs, "Log on as a service"));
                }

                outval = Errors.Any() ? SPHealthCheckStatus.Failed : SPHealthCheckStatus.Passed;


            } // try
            catch (Exception ex)
            {
                LoggingService.Write(
                    LoggingService.Category_C2WTS,
                    TraceSeverity.High,
                    String.Format("Health Check encountered the following exception: {0}", ex.Message),
                    CorrelationToken);
            }

            return outval;
        } // doCheck()

    } // class
} // namespace
